package com.mercury.core.util;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * 日期util.
 *
 * @author mercury
 */
public class DateUtil {

    /**
     * 添加字段注释.
     */
    public static final String ENUM_FORMAT = "yyyy-MM-dd HH:mm:ss";
    /**
     * 添加字段注释.
     */
    public static final String ENUM_FORMAT_YMD = "yyyy-MM-dd";
    /**
     * 添加字段注释.
     */
    public static final String ENUM_FORMAT_YMDS = "yyyy-MM-dd HH:mm:ss.S";
    /**
     * 年月日格式
     */
    public static final String YMDS = "yyyy年MM月dd";
    /**
     * 添加字段注释.
     */
    public static final String ENUM_FORMAT_SLASH = "yyyy/MM/dd HH:mm:ss";
    /**
     * 添加字段注释.
     */
    public static final String ENUM_FORMAT_YMDS_SLASH = "yyyy/MM/dd HH:mm:ss.S";
    /**
     * 添加字段注释.
     */
    public static final String YMD = "yyyyMMdd";

    /**
     * GMT格式pattern
     */
    public static final String GMT = "E, dd MMM yyyy HH:mm:ss z";

    /**
     * pattern HH:mm
     */
    public static final String HM = "HH:mm";

    /**
     * pattern yyyy-MM-dd HH:mm
     */
    public static final String YMDHM = "yyyy-MM-dd HH:mm";

    /**
     * pattern yyyy_MM_dd
     */
    public static final String Y_M_D = "yyyy_MM_dd";

    /**
     * 添加字段注释.
     */
    public static final String LEVEL_DAY = "day"; // 粒度级别
    /**
     * 添加字段注释.
     */
    public static final String LEVEL_HOUR = "hour";
    /**
     * 添加字段注释.
     */
    public static final String LEVEL_MINUTE = "minute";
    /**
     * 添加字段注释.
     */
    public static final String LEVEL_SECOND = "second";
    /**
     * 日期特殊字符对应.
     */
    private static Map<String, String> mapSign = new HashMap<>();
    /**
     * 使用ThreadLocal保证SimpleDateFormat线程安全.
     */
    private static ThreadLocal<Map<String, SimpleDateFormat>> threadLocalDateFormat = new ThreadLocal<>();

    /**
     * 0时区
     */
    public static SimpleTimeZone ZEROTIMEZONE = new SimpleTimeZone(0, "GMT");

    /**
     * 8时区
     */
    public static TimeZone EIGHTTIMEZONE = TimeZone.getTimeZone("GMT+8");

    /**
     * 构造函数.
     */
    public DateUtil() {
        throw new RuntimeException("this is a util class,can not instance!");
    }

    /**
     * 初始化DateFormat标志位.
     */
    private static void initMapSign() {
        if (mapSign.isEmpty()) {
            mapSign.put("上午|下午", "a");
            mapSign.put("星期[一二三四五六日天七]", "E");
            mapSign.put("CST", "z");
            mapSign.put("公元[前]?", "G");
        }
    }

    /**
     * 常规日期格式yyyy-MM-dd HH:mm:ss.
     *
     * @param date date
     * @return time
     */
    public static String format(Date date) {
        return getDateFormat(ENUM_FORMAT).format(date);
    }

    /**
     * 常规日期格式yyyy-MM-dd.
     *
     * @param date date
     * @return time
     */
    public static String formatYmd(Date date) {
        return getDateFormat(ENUM_FORMAT_YMD).format(date);
    }


    /**
     * 格式化时间.
     *
     * @param date       date
     * @param dateFormat dateFormat
     * @return time
     */
    public static String format(Date date, String dateFormat) {
        if (null == date) {
            return null;
        }
        return getDateFormat(dateFormat).format(date);
    }

    /**
     * parse时间(yyyy-MM-dd HH:mm:ss).
     *
     * @param source source
     * @return Date
     * @throws ParseException ParseException
     */
    public static Date parse(String source) throws ParseException {
        return getDateFormat(ENUM_FORMAT).parse(source);
    }

    /**
     * parse时间(yyyy-MM-dd).
     *
     * @param source source
     * @return Date
     * @throws ParseException ParseException
     */
    public static Date parseYmd(String source) throws ParseException {
        return getDateFormat(ENUM_FORMAT_YMD).parse(source);
    }

    /**
     * 格式化时间.
     *
     * @param time       time
     * @param dateFormat dateFormat
     * @return Date
     * @throws ParseException ParseException
     */
    public static Date parse(String time, String dateFormat) throws ParseException {
        if (isNullOrEmpty(time) || isNullOrEmpty(dateFormat)) {
            return null;
        }
        return getDateFormat(dateFormat).parse(time);
    }

    /**
     * 获取指定时间格式的 SimpleDateFormat.
     *
     * @param pattern 时间格式
     * @return SimpleDateFormat
     */
    private static SimpleDateFormat getDateFormat(String pattern) {
        Map<String, SimpleDateFormat> dateFormatMap = threadLocalDateFormat.get();
        if (dateFormatMap == null) {
            dateFormatMap = new HashMap<>(16);
        }
        SimpleDateFormat simpleDateFormat = dateFormatMap.get(pattern);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat(pattern, Locale.getDefault());
            dateFormatMap.put(pattern, simpleDateFormat);
            threadLocalDateFormat.set(dateFormatMap);
        }
        return simpleDateFormat;
    }

    /**
     * 获取指定pattern、locale、timezone的dateFormat
     *
     * @param pattern  时间格式 {@link DateUtil#GMT}
     * @param locale   the locale whose date format symbols should be used {@link Locale#UK}
     * @param timeZone 时区 {@link DateUtil#ZEROTIMEZONE}
     * @return
     */
    public static SimpleDateFormat getDateFormat(String pattern, Locale locale, TimeZone timeZone) {
        Map<String, SimpleDateFormat> dateFormatMap = threadLocalDateFormat.get();
        if (dateFormatMap == null) {
            dateFormatMap = new HashMap<>(16);
        }
        SimpleDateFormat simpleDateFormat = dateFormatMap.get(pattern);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat(pattern, locale);
            simpleDateFormat.setTimeZone(timeZone);
            dateFormatMap.put(pattern, simpleDateFormat);
            threadLocalDateFormat.set(dateFormatMap);
        }
        return simpleDateFormat;
    }


    public static LocalDateTime getStartDateTime(LocalDate localDate) {
        if (Objects.isNull(localDate)) {
            return null;
        }
        return LocalDateTime.of(localDate, LocalTime.MIN);
    }

    public static LocalDateTime getEndDateTime(LocalDate localDate) {
        if (Objects.isNull(localDate)) {
            return null;
        }
        return LocalDateTime.of(localDate, LocalTime.MAX);
    }


    /**
     * 将源时区时间转化为目标时区时间
     *
     * @param sourceDate     源时区时间
     * @param sourceTimeZone 源时区
     * @param targetTimeZone 目标时区
     * @return
     */
    public static Date convertTimeZone(final Date sourceDate, final TimeZone sourceTimeZone, final TimeZone targetTimeZone) {
        long sourceTimeStamp = sourceDate.getTime();
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(sourceTimeZone);
        long sourceZoneOffSet = calendar.get(Calendar.ZONE_OFFSET);
        long sourceDstOffSet = calendar.get(Calendar.DST_OFFSET);
        calendar.setTimeZone(targetTimeZone);
        long targetZoneOffSet = calendar.get(Calendar.ZONE_OFFSET);
        long targetDstOffSet = calendar.get(Calendar.DST_OFFSET);
        long targetTimeStamp = sourceTimeStamp + (targetZoneOffSet + targetDstOffSet) - (sourceZoneOffSet + sourceDstOffSet);
        return new Date(targetTimeStamp);
    }

    /**
     * 自动解析时间格式并parse（时间格式为yyyyMMddHHmmssS，默认24小时制、前包含且必须包含yyyyMMdd）.
     *
     * @param time time
     * @return Date
     * @throws ParseException ParseException
     */
    public static Date parseAuto(String time) throws ParseException {
        if (isNullOrEmpty(time) || time.length() < 8) {
            return null;
        }
        initMapSign();
        time = time.trim();
        String formatPattern = "";
        if (time.matches("[\\d]+")) { // 纯数字
            String all = "yyyyMMddHHmmssSSS";
            if (time.length() > all.length()) { // 超长截取
                time = time.substring(0, all.length());
            }
            formatPattern = all.substring(0, time.length());
        } else {
            char next = 'y';
            String idNext = "yMdHmsS";
            StringBuilder buffer = new StringBuilder();
            for (char var : time.toCharArray()) {
                if (String.valueOf(var).matches("[0-9]")) {
                    buffer.append(next);
                } else if ("T".equals(String.valueOf(var))) {
                    buffer.append("'").append(var).append("'");
                } else {
                    buffer.append(var);
                    next = idNext.charAt(Math.min(idNext.indexOf(next) + 1, idNext.length() - 1));
                }
            }
            formatPattern = buffer.toString();
        }
        for (Entry<String, String> entry : mapSign.entrySet()) {
            formatPattern = formatPattern.replaceAll(entry.getKey(), entry.getValue());
        }
        return parse(time, formatPattern);
    }

    /**
     * 是否为空或"".
     *
     * @param param param
     * @return boolean
     */
    private static boolean isNullOrEmpty(String param) {
        return null == param || "".equals(param.trim());
    }

    /**
     * 日期增加num天.
     *
     * @param date date
     * @param num  加减天数
     * @return Date
     */
    public static Date addDate(Date date, int num) {
        return addDate(date, Calendar.DATE, num);
    }

    /**
     * 时间增加.
     *
     * @param date     date
     * @param calendar 加减级别Calendar
     * @param num      加减天数
     * @return Date
     */
    public static Date addDate(Date date, int calendar, int num) {
        if (null == date || 0 == num) {
            return date;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(calendar, num);
        return cal.getTime();
    }

    /**
     * 保留日期到某一级别（天、时、分、秒...）.
     *
     * @param date  date
     * @param level 保留级别，null保留到day
     * @return date
     */
    public static Date setDate(Date date, String level) {
        if (null == date || null == level) {
            return date;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        switch (level) {
            case LEVEL_DAY: // 保留到 Day
                // cal.set(Calendar.HOUR, 0); // 12小时制
                cal.set(Calendar.HOUR_OF_DAY, 0); // 24小时制
                cal.set(Calendar.MINUTE, 0);
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);
                break;
            case LEVEL_HOUR: // 保留到 Hour
                cal.set(Calendar.MINUTE, 0);
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);
                break;
            case LEVEL_MINUTE: // 保留到 MINUTE
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);
                break;
            case LEVEL_SECOND: // 保留到 SECOND
                cal.set(Calendar.MILLISECOND, 0);
                break;
            default:
                break;
        }
        return cal.getTime();
    }

    /**
     * 比较两个日期的间隔（时间差绝对值,向下取整）（day、hour、minute）.
     *
     * @param date1 date1
     * @param date2 date2
     * @param level 比较级别
     * @return int 无对应时间间隔级别
     */
    public static Integer getDateInterval(Date date1, Date date2, String level) {
        Double num = dateInterval(date1, date2, level);
        if (null == num) {
            return null;
        }
        return (int) Math.floor(num);
    }

    /**
     * 比较两个日期的间隔（时间差绝对值,向上取整）（day、hour、minute）.
     *
     * @param date1 date1
     * @param date2 date2
     * @param level 比较级别
     * @return int 无对应时间间隔级别
     */
    public static Integer getDateIntervalCeil(Date date1, Date date2, String level) {
        Double num = dateInterval(date1, date2, level);
        if (null == num) {
            return null;
        }
        return (int) Math.ceil(num);
    }


    /**
     * 获取两个localDate中间所有值 从小到大
     */
    public static List<LocalDate> getAscDateList(LocalDate startDate, LocalDate endDate) {
        List<LocalDate> result = new ArrayList<>();
        if (endDate.compareTo(startDate) < 0) {
            return result;
        }
        while (true) {
            result.add(startDate);
            if (startDate.compareTo(endDate) >= 0) {
                break;
            }
            startDate = startDate.plusDays(1);
        }
        return result;

    }


    /**
     * 获取两个localDate中间所有值 从大到小
     */
    public static List<LocalDate> getDescDateList(LocalDate startDate, LocalDate endDate) {
        List<LocalDate> result = new ArrayList<>();
        if (endDate.compareTo(startDate) < 0) {
            return result;
        }
        while (true) {
            result.add(endDate);
            if (endDate.compareTo(startDate) <= 0) {
                break;
            }
            endDate = endDate.plusDays(-1);
        }
        return result;
    }


    /**
     * 比较两个日期的间隔（day、hour、minute）.
     *
     * @param date1 date1
     * @param date2 date2
     * @param level 比较级别
     * @return int 无对应时间间隔级别
     */
    private static Double dateInterval(Date date1, Date date2, String level) {
        Double time = (double) (date1.getTime() - date2.getTime());
        if (time < 0) {
            time = time * -1;
        }
        Double num = null;
        switch (level) {
            case LEVEL_DAY: // 天
                num = (Double) (time / TimeUnit.DAYS.toMillis(1));
                break;
            case LEVEL_HOUR: // 小时
                num = (Double) (time / TimeUnit.HOURS.toMillis(1));
                break;
            case LEVEL_MINUTE: // 分钟
                num = (Double) (time / TimeUnit.MINUTES.toMillis(1));
                break;
            case LEVEL_SECOND: // 秒
                num = (Double) (time / TimeUnit.SECONDS.toMillis(1));
                break;
            default:
                break;
        }
        return num;
    }

    /**
     * 获取当前日期指定时间.
     *
     * @param date date
     * @param time time
     * @return date
     * @throws ParseException ParseException
     */
    public static Date dateToHms(Date date, String time) throws ParseException {
        if (null == date || isNullOrEmpty(time)) {
            return date;
        }
        StringBuilder timeBuf = new StringBuilder();
        String dateStr = formatYmd(date);
        timeBuf.append(dateStr).append(" ");
        time = time.trim();
        while (!time.matches(".*\\d")) {
            time = time.substring(0, time.length() - 1);
        }
        timeBuf.append(time);
        if (time.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
            timeBuf.append(".0");
        } else if (time.matches("\\d{1,2}:\\d{1,2}")) {
            timeBuf.append(":00.0");
        } else if (time.matches("\\d{1,2}")) {
            timeBuf.append(":00:00.0");
        }
        return parse(timeBuf.toString(), ENUM_FORMAT_YMDS);
    }


    public static String formatLocalDateTime(LocalDateTime localDateTime) {
        if (Objects.nonNull(localDateTime)) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            return dateTimeFormatter.format(localDateTime);
        }
        return null;
    }

    public static String formatLocalDate(LocalDate localDate) {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return localDate.format(fmt);
    }

    /**
     * 将Date类转换为XMLGregorianCalendar.
     *
     * @param date date
     * @return XMLGregorianCalendar
     * @throws DatatypeConfigurationException DatatypeConfigurationException
     */
    public static XMLGregorianCalendar dateToXmlDate(Date date)
            throws DatatypeConfigurationException {
        XMLGregorianCalendar dateType = null;
        if (null != date) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            DatatypeFactory dtf = DatatypeFactory.newInstance();
            dateType = dtf.newXMLGregorianCalendar();
            if (null != dateType) {
                dateType.setYear(cal.get(Calendar.YEAR));
                // 由于Calendar.MONTH取值范围为0~11,需要加1
                dateType.setMonth(cal.get(Calendar.MONTH) + 1);
                dateType.setDay(cal.get(Calendar.DAY_OF_MONTH));
                dateType.setHour(cal.get(Calendar.HOUR_OF_DAY));
                dateType.setMinute(cal.get(Calendar.MINUTE));
                dateType.setSecond(cal.get(Calendar.SECOND));
            }
        }
        return dateType;
    }

    /**
     * 清理ThreadLocal（每次线程结束都应执行此操作）.
     */
    public static void clearThreadLocal() {
        threadLocalDateFormat.remove();
    }

    /**
     * 当前时间与下午13点进行比较
     *
     * @return
     */
    public static boolean compareHour() {
        return LocalTime.now().getHour() >= 13;
    }

    public static Date nowTimeZero(Date date) {
        Date zeroTime = null;
        try {
            zeroTime = parseYmd(formatYmd(date));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return zeroTime;
    }

    //昨天凌晨
    public static Date getYesData() {
        LocalDateTime localDateTime = LocalDateTime.now().minusDays(1);
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDateTime.atZone(zoneId);
        return nowTimeZero(Date.from(zdt.toInstant()));
    }

    //明天凌晨
    public static Date getTomoData() {
        LocalDateTime localDateTime = LocalDateTime.now().plusDays(1);
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDateTime.atZone(zoneId);
        return nowTimeZero(Date.from(zdt.toInstant()));
    }

    //当日开始时间
    public static String getStartTime() {
        Calendar todayStart = Calendar.getInstance();
        todayStart.set(Calendar.HOUR_OF_DAY, 0);
        todayStart.set(Calendar.MINUTE, 0);
        todayStart.set(Calendar.SECOND, 0);
        return format(todayStart.getTime());
    }

    //当日结束时间
    public static String getEndTime() {
        Calendar todayEnd = Calendar.getInstance();
        todayEnd.set(Calendar.HOUR_OF_DAY, 23);
        todayEnd.set(Calendar.MINUTE, 59);
        todayEnd.set(Calendar.SECOND, 59);
        return format(todayEnd.getTime());
    }

    /**
     * 时间戳转date
     */
    public static Date timeStampToDate(Long seconds) {
        Date date = new Date(seconds);
        try {
            return parse(new SimpleDateFormat(ENUM_FORMAT).format(date));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取2个日期 区间日期列表
     *
     * @param startDate
     * @param endDate
     * @return java.util.Set<java.time.LocalDate>
     * @author mercury
     * @date 2021/04/19 14:30
     */
    public static Set<LocalDate> buildSectionDate(LocalDate startDate, LocalDate endDate) {
        Set<LocalDate> dateList = new HashSet<>();
        long days = endDate.toEpochDay() - startDate.toEpochDay();
        if (days > 0) {
            for (int i = 1; i <= days; i++) {
                dateList.add(startDate.plusDays(i));
            }
        }
        return dateList;
    }


    public static Boolean checkTime(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDate startLocalDate = startTime.toLocalDate();
        int startHour = startTime.getHour();
        int startMinute = startTime.getMinute();

        LocalDate endLocalDate = endTime.toLocalDate();
        int sendHour = endTime.getHour();
        int endMinute = endTime.getMinute();

        return startLocalDate.isEqual(endLocalDate) && startHour == sendHour && startMinute == endMinute;
    }
}